#include "Engine.h"
#include "Model.h"
#include "FrameBuffer.h"
#include "CubeMapShadowMapFrameBuffer.h"
#include "Skybox.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <GLFW/glfw3.h>

namespace OCPP
{
	Engine::Engine() :
		mWindow(NULL),
		mDynLibManager(NULL)
	{

	}

	Engine::~Engine()
	{
		CORE_SAFE_DELETE(mDynLibManager);
		CORE_SAFE_DELETE(mWindow);
		if (bInitialized)
		{
			glfwTerminate();
		}
	}

	Vector3 camePos;

	// Compare function
	bool compare(const Matrix4& m1, const Matrix4& m2)
	{
		//double diff = (camePos - m2.getTrans()).length() - (camePos - m1.getTrans()).length();
		//int i = (0 < diff) - (diff < 0);
		return (camePos - m2.getTrans()).length() <  (camePos - m1.getTrans()).length();
	};

	void Engine::start()
	{
		// Number of samples per pixel for MSAA
		init();

		Skybox skybox;

		Camera camera;
		State shaderState(camera);
		camera.setPosition(Vector3(0.0f, 0.0f, 2.0f));
		camera.setLookAt(Vector3(0.0f, 0.0f, -1.0f));

		Matrix4List instances;
		instances.push_back(Matrix4::IDENTITY);

		Shader sceneShader;
		sceneShader.load("shaders/default.vert", "shaders/default.frag", "shaders/default.geom");

		Shader shadowMapShader;
		shadowMapShader.load("shaders/shadowMap.vert", "shaders/shadowMap.frag");

		Shader shadowCubeMapProgram;
		shadowCubeMapProgram.load("shaders/shadowCubeMap.vert", "shaders/shadowCubeMap.frag", "shaders/shadowCubeMap.geom");



		//for (int i = 0; i < 100; i++)
		//{
		//	Matrix4 m2 = Matrix4::IDENTITY;
		//	m2.makeTrans(Vector3(i, -i * 10, 0));
		//	instances.push_back(m2);
		//}

		Model model;
		//model.setInstanceMatrix(instances);
		model.addMesh(MeshBuilder::buildRect());
		//model.load("shaders/models/trees/scene.gltf");


		Model model2;
		model2.setInstanceMatrix(instances);
		model2.load("shaders/models/ground/scene.gltf");

		ShadowMapFrameBuffer mShadowMapFrameBuffer(2048, 2048);
		CubeMapShadowMapFrameBuffer mCubeMapShadowMapFrameBuffer(2048, 2048);
		FrameBuffer frameBuffer(mWindow->getWidth(), mWindow->getHeight());

		//std::vector<Matrix4> winMatrixList;

		//const unsigned int numWindows = 100;
		//for (unsigned int i = 0; i < numWindows; i++)
		//{
		//	Vector3 pos
		//	(
		//		-15.0f + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (15.0f - (-15.0f)))),
		//		1.0f + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (4.0f - 1.0f))),
		//		-15.0f + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / (15.0f - (-15.0f))))
		//	);
		//	float qy = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / 1.0f));
		//	Matrix4 m;
		//	m.makeTransform(pos, Vector3::UNIT_SCALE, Quaternion(1, 0, qy, 0));
		//	winMatrixList.push_back(m);
		//}

		//Model model3;
		//model3.load("shaders/models/windows/scene.gltf");
		//model3.loadShader("shaders/default.vert", "shaders/default.frag");


		// Variables to create periodic event for FPS displaying
		double prevTime = 0.0;
		double crntTime = 0.0;
		double timeDiff;
		// Keeps track of the amount of frames in timeDiff
		unsigned int counter = 0;

		// Main while loop
		while (!mWindow->shouldClose())
		{
			camePos = camera.getPosition();

			// Updates counter and times
			crntTime = glfwGetTime();
			timeDiff = crntTime - prevTime;
			counter++;

			if (timeDiff >= 1.0 / 60.0)
			{
				// Creates new title
				String FPS = std::to_string((int)(1.0 / timeDiff) * counter);
				String ms = std::to_string((timeDiff / counter) * 1000);
				String newTitle = "YoutubeOpenGL - " + FPS + "FPS / " + ms + "ms";
				glfwSetWindowTitle(mWindow->getWindow(), newTitle.c_str());

				// Resets times and counter
				prevTime = crntTime;
				counter = 0;

				glEnable(GL_DEPTH_TEST);

				//shadow
				//shadowMapShader.Activate();
				//shaderState.updateState(shadowMapShader);
				//mShadowMapFrameBuffer.bind();
				//glClear(GL_DEPTH_BUFFER_BIT);
				//model.draw(camera, shaderState, shadowMapShader);
				//model2.draw(camera, shaderState, shadowMapShader);
				//mShadowMapFrameBuffer.unbind();

				//shadowCubeMapProgram.Activate();
				//shaderState.updateState(shadowCubeMapProgram);
				//mCubeMapShadowMapFrameBuffer.bind();
				//glClear(GL_DEPTH_BUFFER_BIT);
				//model2.draw(camera, shaderState, shadowCubeMapProgram);
				//model.draw(camera, shaderState, shadowCubeMapProgram);
				//mCubeMapShadowMapFrameBuffer.unbind();


				//scene
				mWindow->makeViewport();

				frameBuffer.bind();
				// Specify the color of the background
				glClearColor(0.07f, 0.13f, 0.17f, 1.0f);
				// Clean the back buffer and assign the new color to it
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

				glEnable(GL_DEPTH_TEST);

				camera.Inputs(mWindow->getWindow());

				skybox.draw(camera);

				sceneShader.Activate();

				//glActiveTexture(GL_TEXTURE0 + 2);
				//glBindTexture(GL_TEXTURE_2D, mShadowMapFrameBuffer.shadowMap);
				//sceneShader.setUniform1i("shadowMap", 2);

				//glActiveTexture(GL_TEXTURE0 + 3);
				//glBindTexture(GL_TEXTURE_CUBE_MAP, mCubeMapShadowMapFrameBuffer.shadowMap);
				//sceneShader.setUniform1i("shadowCubeMap", 3);

				shaderState.updateState(sceneShader);

				model.draw(camera, shaderState, sceneShader);
				//model2.draw(camera, shaderState, sceneShader);
				//
				frameBuffer.draw();

				//glDisable(GL_CULL_FACE);
				//model2.draw(camera, shaderState);

				//glEnable(GL_BLEND);
				//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				//std::sort(winMatrixList.begin(), winMatrixList.end(), compare);
				//for (auto& m : winMatrixList)
				//{
				//	model3.setMatrix(m);
				//	model3.draw(camera, shaderState);
				//}
				//glDisable(GL_BLEND);
				//glEnable(GL_CULL_FACE);

				// Swap the back buffer with the front buffer
				glfwSwapBuffers(mWindow->getWindow());

				// Take care of all GLFW events
				glfwPollEvents();
			}

		}
	}

	void Engine::init()
	{
		if (bInitialized)
		{
			return;
		}

		bInitialized = true;

		loadPlugins();

		// Initialize GLFW
		glfwInit();

		// Tell GLFW what version of OpenGL we are using 
		// In this case we are using OpenGL 3.3
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
		// Tell GLFW we are using the CORE profile
		// So that means we only have the modern functions
		glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

		mWindow = new Window(800, 800);

		// Create a GLFWwindow object of 800 by 800 pixels, naming it "YoutubeOpenGL"

		// Introduce the window into the current context
		mWindow->makeContextCurrent();


		//Load GLAD so it configures OpenGL
		gladLoadGL();
		// Specify the viewport of OpenGL in the Window
		// In this case the viewport goes from x = 0, y = 0, to x = 800, y = 800
		
		mWindow->createViewport(0, 0, 800, 800);
		mWindow->makeViewport();	

		// Enables the Depth Buffer
		glEnable(GL_DEPTH_TEST);
		// glDepthFunc(GL_LESS);

		// Enables the Stencil Buffer
		// glEnable(GL_STENCIL_TEST);
		// Sets rules for outcomes of stecil tests
		// glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

		glEnable(GL_CULL_FACE);
		//glDisable(GL_CULL_FACE);

		glCullFace(GL_BACK);
		// Uses counter clock-wise standard
		glFrontFace(GL_CCW);

		// Enables Multisampling
		glEnable(GL_MULTISAMPLE);
	}

	typedef void (*DLL_START_PLUGIN)(void);
	typedef void (*DLL_STOP_PLUGIN)(void);

	void Engine::loadPlugins()
	{
		mDynLibManager = new DynLibManager();
		String path = Singleton(Environment).getAppPath();

#if _DEBUG
		DynLib* lib = Singleton(DynLibManager).load(path + "OCPP_FreeImageCodecd");
#else
		DynLib* lib = Singleton(DynLibManager).load(path + "OCPP_FreeImageCodec");
#endif
		if (lib)
		{
			DLL_START_PLUGIN pFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin");
			if (pFunc)
			{
				pFunc();
			}
		}
	}
}